/*
 * Copyright 2004-2011 H2 Group.
 * Copyright 2011 James Moger.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.iciql.bytecode;

import com.iciql.IciqlException;
import com.iciql.Token;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

/**
 * This class converts a method to a SQL Token by interpreting (decompiling) the
 * bytecode of the class.
 */
public class ClassReader {

    private static final boolean DEBUG = false;

    private byte[] data;
    private int pos;
    private Constant[] constantPool;
    private int startByteCode;
    private String methodName;

    private String convertMethodName;
    private Token result;
    private Stack<Token> stack = new Stack<Token>();
    private ArrayList<Token> variables = new ArrayList<Token>();
    private boolean endOfMethod;
    private boolean condition;
    private int nextPc;
    private Map<String, Object> fieldMap = new HashMap<String, Object>();

    private static void debug(String s) {
        if (DEBUG) {
            System.out.println(s);
        }
    }

    public Token decompile(Object instance, Map<String, Object> fields, String method) {
        this.fieldMap = fields;
        this.convertMethodName = method;
        Class<?> clazz = instance.getClass();
        String className = clazz.getName();
        debug("class name " + className);
        ByteArrayOutputStream buff = new ByteArrayOutputStream();
        try {
            InputStream in = clazz.getClassLoader().getResource(className.replace('.', '/') + ".class")
                    .openStream();
            while (true) {
                int x = in.read();
                if (x < 0) {
                    break;
                }
                buff.write(x);
            }
        } catch (IOException e) {
            throw new IciqlException("Could not read class bytecode", e);
        }
        data = buff.toByteArray();
        int header = readInt();
        debug("header: " + Integer.toHexString(header));
        int minorVersion = readShort();
        int majorVersion = readShort();
        debug("version: " + majorVersion + "." + minorVersion);
        int constantPoolCount = readShort();
        constantPool = new Constant[constantPoolCount];
        for (int i = 1; i < constantPoolCount; i++) {
            int type = readByte();
            switch (type) {
                case 1:
                    constantPool[i] = ConstantString.get(readString());
                    break;
                case 3: {
                    int x = readInt();
                    constantPool[i] = ConstantNumber.get(x);
                    break;
                }
                case 4: {
                    int x = readInt();
                    constantPool[i] = ConstantNumber.get("" + Float.intBitsToFloat(x), x, Constant.Type.FLOAT);
                    break;
                }
                case 5: {
                    long x = readLong();
                    constantPool[i] = ConstantNumber.get(x);
                    i++;
                    break;
                }
                case 6: {
                    long x = readLong();
                    constantPool[i] = ConstantNumber
                            .get("" + Double.longBitsToDouble(x), x, Constant.Type.DOUBLE);
                    i++;
                    break;
                }
                case 7: {
                    int x = readShort();
                    constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.CLASS_REF);
                    break;
                }
                case 8: {
                    int x = readShort();
                    constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.STRING_REF);
                    break;
                }
                case 9: {
                    int x = readInt();
                    constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.FIELD_REF);
                    break;
                }
                case 10: {
                    int x = readInt();
                    constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.METHOD_REF);
                    break;
                }
                case 11: {
                    int x = readInt();
                    constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.INTERFACE_METHOD_REF);
                    break;
                }
                case 12: {
                    int x = readInt();
                    constantPool[i] = ConstantNumber.get(null, x, ConstantNumber.Type.NAME_AND_TYPE);
                    break;
                }
                default:
                    throw new IciqlException("Unsupported constant pool tag: " + type);
            }
        }
        int accessFlags = readShort();
        debug("access flags: " + accessFlags);
        int classRef = readShort();
        debug("class: " + constantPool[constantPool[classRef].intValue()]);
        int superClassRef = readShort();
        debug(" extends " + constantPool[constantPool[superClassRef].intValue()]);
        int interfaceCount = readShort();
        for (int i = 0; i < interfaceCount; i++) {
            int interfaceRef = readShort();
            debug(" implements " + constantPool[constantPool[interfaceRef].intValue()]);
        }
        int fieldCount = readShort();
        for (int i = 0; i < fieldCount; i++) {
            readField();
        }
        int methodCount = readShort();
        for (int i = 0; i < methodCount; i++) {
            readMethod();
        }
        readAttributes();
        return result;
    }

    private void readField() {
        int accessFlags = readShort();
        int nameIndex = readShort();
        int descIndex = readShort();
        debug("    " + constantPool[descIndex] + " " + constantPool[nameIndex] + " " + accessFlags);
        readAttributes();
    }

    private void readMethod() {
        int accessFlags = readShort();
        int nameIndex = readShort();
        int descIndex = readShort();
        String desc = constantPool[descIndex].toString();
        methodName = constantPool[nameIndex].toString();
        debug("    " + desc + " " + methodName + " " + accessFlags);
        readAttributes();
    }

    private void readAttributes() {
        int attributeCount = readShort();
        for (int i = 0; i < attributeCount; i++) {
            int attributeNameIndex = readShort();
            String attributeName = constantPool[attributeNameIndex].toString();
            debug("        attribute " + attributeName);
            int attributeLength = readInt();
            int end = pos + attributeLength;
            if ("Code".equals(attributeName)) {
                readCode();
            }
            pos = end;
        }
    }

    void decompile() {
        int maxStack = readShort();
        int maxLocals = readShort();
        debug("stack: " + maxStack + " locals: " + maxLocals);
        int codeLength = readInt();
        startByteCode = pos;
        int end = pos + codeLength;
        while (pos < end) {
            readByteCode();
        }
        debug("");
        pos = startByteCode + codeLength;
        int exceptionTableLength = readShort();
        pos += 2 * exceptionTableLength;
        readAttributes();
    }

    private void readCode() {
        variables.clear();
        stack.clear();
        int maxStack = readShort();
        int maxLocals = readShort();
        debug("stack: " + maxStack + " locals: " + maxLocals);
        int codeLength = readInt();
        startByteCode = pos;
        if (methodName.startsWith(convertMethodName)) {
            result = getResult();
        }
        pos = startByteCode + codeLength;
        int exceptionTableLength = readShort();
        pos += 2 * exceptionTableLength;
        readAttributes();
    }

    private Token getResult() {
        while (true) {
            readByteCode();
            if (endOfMethod) {
                return stack.pop();
            }
            if (condition) {
                Token c = stack.pop();
                Stack<Token> currentStack = new Stack<Token>();
                currentStack.addAll(stack);
                ArrayList<Token> currentVariables = new ArrayList<Token>();
                currentVariables.addAll(variables);
                int branch = nextPc;
                Token a = getResult();
                stack = currentStack;
                variables = currentVariables;
                pos = branch + startByteCode;
                Token b = getResult();
                if (a.equals("0") && b.equals("1")) {
                    return c;
                } else if (a.equals("1") && b.equals("0")) {
                    return Not.get(c);
                } else if (b.equals("0")) {
                    return And.get(Not.get(c), a);
                } else if (a.equals("0")) {
                    return And.get(c, b);
                } else if (b.equals("1")) {
                    return Or.get(c, a);
                } else if (a.equals("1")) {
                    return And.get(Not.get(c), b);
                }
                return CaseWhen.get(c, b, a);
            }
            if (nextPc != 0) {
                pos = nextPc + startByteCode;
            }
        }
    }

    private void readByteCode() {
        int startPos = pos - startByteCode;
        int opCode = readByte();
        String op;
        endOfMethod = false;
        condition = false;
        nextPc = 0;
        switch (opCode) {
            case 0:
                op = "nop";
                break;
            case 1:
                op = "aconst_null";
                stack.push(Null.INSTANCE);
                break;
            case 2:
                op = "iconst_m1";
                stack.push(ConstantNumber.get("-1"));
                break;
            case 3:
                op = "iconst_0";
                stack.push(ConstantNumber.get("0"));
                break;
            case 4:
                op = "iconst_1";
                stack.push(ConstantNumber.get("1"));
                break;
            case 5:
                op = "iconst_2";
                stack.push(ConstantNumber.get("2"));
                break;
            case 6:
                op = "iconst_3";
                stack.push(ConstantNumber.get("3"));
                break;
            case 7:
                op = "iconst_4";
                stack.push(ConstantNumber.get("4"));
                break;
            case 8:
                op = "iconst_5";
                stack.push(ConstantNumber.get("5"));
                break;
            case 9:
                op = "lconst_0";
                stack.push(ConstantNumber.get("0"));
                break;
            case 10:
                op = "lconst_1";
                stack.push(ConstantNumber.get("1"));
                break;
            case 11:
                op = "fconst_0";
                stack.push(ConstantNumber.get("0.0"));
                break;
            case 12:
                op = "fconst_1";
                stack.push(ConstantNumber.get("1.0"));
                break;
            case 13:
                op = "fconst_2";
                stack.push(ConstantNumber.get("2.0"));
                break;
            case 14:
                op = "dconst_0";
                stack.push(ConstantNumber.get("0.0"));
                break;
            case 15:
                op = "dconst_1";
                stack.push(ConstantNumber.get("1.0"));
                break;
            case 16: {
                int x = (byte) readByte();
                op = "bipush " + x;
                stack.push(ConstantNumber.get(x));
                break;
            }
            case 17: {
                int x = (short) readShort();
                op = "sipush " + x;
                stack.push(ConstantNumber.get(x));
                break;
            }
            case 18: {
                Token s = getConstant(readByte());
                op = "ldc " + s;
                stack.push(s);
                break;
            }
            case 19: {
                Token s = getConstant(readShort());
                op = "ldc_w " + s;
                stack.push(s);
                break;
            }
            case 20: {
                Token s = getConstant(readShort());
                op = "ldc2_w " + s;
                stack.push(s);
                break;
            }
            case 21: {
                int x = readByte();
                op = "iload " + x;
                stack.push(getVariable(x));
                break;
            }
            case 22: {
                int x = readByte();
                op = "lload " + x;
                stack.push(getVariable(x));
                break;
            }
            case 23: {
                int x = readByte();
                op = "fload " + x;
                stack.push(getVariable(x));
                break;
            }
            case 24: {
                int x = readByte();
                op = "dload " + x;
                stack.push(getVariable(x));
                break;
            }
            case 25: {
                int x = readByte();
                op = "aload " + x;
                stack.push(getVariable(x));
                break;
            }
            case 26:
                op = "iload_0";
                stack.push(getVariable(0));
                break;
            case 27:
                op = "iload_1";
                stack.push(getVariable(1));
                break;
            case 28:
                op = "iload_2";
                stack.push(getVariable(2));
                break;
            case 29:
                op = "iload_3";
                stack.push(getVariable(3));
                break;
            case 30:
                op = "lload_0";
                stack.push(getVariable(0));
                break;
            case 31:
                op = "lload_1";
                stack.push(getVariable(1));
                break;
            case 32:
                op = "lload_2";
                stack.push(getVariable(2));
                break;
            case 33:
                op = "lload_3";
                stack.push(getVariable(3));
                break;
            case 34:
                op = "fload_0";
                stack.push(getVariable(0));
                break;
            case 35:
                op = "fload_1";
                stack.push(getVariable(1));
                break;
            case 36:
                op = "fload_2";
                stack.push(getVariable(2));
                break;
            case 37:
                op = "fload_3";
                stack.push(getVariable(3));
                break;
            case 38:
                op = "dload_0";
                stack.push(getVariable(0));
                break;
            case 39:
                op = "dload_1";
                stack.push(getVariable(1));
                break;
            case 40:
                op = "dload_2";
                stack.push(getVariable(2));
                break;
            case 41:
                op = "dload_3";
                stack.push(getVariable(3));
                break;
            case 42:
                op = "aload_0";
                stack.push(getVariable(0));
                break;
            case 43:
                op = "aload_1";
                stack.push(getVariable(1));
                break;
            case 44:
                op = "aload_2";
                stack.push(getVariable(2));
                break;
            case 45:
                op = "aload_3";
                stack.push(getVariable(3));
                break;
            case 46: {
                Token index = stack.pop();
                Token ref = stack.pop();
                op = "iaload";
                stack.push(ArrayGet.get(ref, index));
                break;
            }
            case 47: {
                Token index = stack.pop();
                Token ref = stack.pop();
                op = "laload";
                stack.push(ArrayGet.get(ref, index));
                break;
            }
            case 48: {
                Token index = stack.pop();
                Token ref = stack.pop();
                op = "faload";
                stack.push(ArrayGet.get(ref, index));
                break;
            }
            case 49: {
                Token index = stack.pop();
                Token ref = stack.pop();
                op = "daload";
                stack.push(ArrayGet.get(ref, index));
                break;
            }
            case 50: {
                Token index = stack.pop();
                Token ref = stack.pop();
                op = "aaload";
                stack.push(ArrayGet.get(ref, index));
                break;
            }
            case 51: {
                Token index = stack.pop();
                Token ref = stack.pop();
                op = "baload";
                stack.push(ArrayGet.get(ref, index));
                break;
            }
            case 52: {
                Token index = stack.pop();
                Token ref = stack.pop();
                op = "caload";
                stack.push(ArrayGet.get(ref, index));
                break;
            }
            case 53: {
                Token index = stack.pop();
                Token ref = stack.pop();
                op = "saload";
                stack.push(ArrayGet.get(ref, index));
                break;
            }
            case 54: {
                int var = readByte();
                op = "istore " + var;
                setVariable(var, stack.pop());
                break;
            }
            case 55: {
                int var = readByte();
                op = "lstore " + var;
                setVariable(var, stack.pop());
                break;
            }
            case 56: {
                int var = readByte();
                op = "fstore " + var;
                setVariable(var, stack.pop());
                break;
            }
            case 57: {
                int var = readByte();
                op = "dstore " + var;
                setVariable(var, stack.pop());
                break;
            }
            case 58: {
                int var = readByte();
                op = "astore " + var;
                setVariable(var, stack.pop());
                break;
            }
            case 59:
                op = "istore_0";
                setVariable(0, stack.pop());
                break;
            case 60:
                op = "istore_1";
                setVariable(1, stack.pop());
                break;
            case 61:
                op = "istore_2";
                setVariable(2, stack.pop());
                break;
            case 62:
                op = "istore_3";
                setVariable(3, stack.pop());
                break;
            case 63:
                op = "lstore_0";
                setVariable(0, stack.pop());
                break;
            case 64:
                op = "lstore_1";
                setVariable(1, stack.pop());
                break;
            case 65:
                op = "lstore_2";
                setVariable(2, stack.pop());
                break;
            case 66:
                op = "lstore_3";
                setVariable(3, stack.pop());
                break;
            case 67:
                op = "fstore_0";
                setVariable(0, stack.pop());
                break;
            case 68:
                op = "fstore_1";
                setVariable(1, stack.pop());
                break;
            case 69:
                op = "fstore_2";
                setVariable(2, stack.pop());
                break;
            case 70:
                op = "fstore_3";
                setVariable(3, stack.pop());
                break;
            case 71:
                op = "dstore_0";
                setVariable(0, stack.pop());
                break;
            case 72:
                op = "dstore_1";
                setVariable(1, stack.pop());
                break;
            case 73:
                op = "dstore_2";
                setVariable(2, stack.pop());
                break;
            case 74:
                op = "dstore_3";
                setVariable(3, stack.pop());
                break;
            case 75:
                op = "astore_0";
                setVariable(0, stack.pop());
                break;
            case 76:
                op = "astore_1";
                setVariable(1, stack.pop());
                break;
            case 77:
                op = "astore_2";
                setVariable(2, stack.pop());
                break;
            case 78:
                op = "astore_3";
                setVariable(3, stack.pop());
                break;
            case 79: {
                // String value = stack.pop();
                // String index = stack.pop();
                // String ref = stack.pop();
                op = "iastore";
                // TODO side effect - not supported
                break;
            }
            case 80:
                op = "lastore";
                // TODO side effect - not supported
                break;
            case 81:
                op = "fastore";
                // TODO side effect - not supported
                break;
            case 82:
                op = "dastore";
                // TODO side effect - not supported
                break;
            case 83:
                op = "aastore";
                // TODO side effect - not supported
                break;
            case 84:
                op = "bastore";
                // TODO side effect - not supported
                break;
            case 85:
                op = "castore";
                // TODO side effect - not supported
                break;
            case 86:
                op = "sastore";
                // TODO side effect - not supported
                break;
            case 87:
                op = "pop";
                stack.pop();
                break;
            case 88:
                op = "pop2";
                // TODO currently we don't know the stack types
                stack.pop();
                stack.pop();
                break;
            case 89: {
                op = "dup";
                Token x = stack.pop();
                stack.push(x);
                stack.push(x);
                break;
            }
            case 90: {
                op = "dup_x1";
                Token a = stack.pop();
                Token b = stack.pop();
                stack.push(a);
                stack.push(b);
                stack.push(a);
                break;
            }
            case 91: {
                // TODO currently we don't know the stack types
                op = "dup_x2";
                Token a = stack.pop();
                Token b = stack.pop();
                Token c = stack.pop();
                stack.push(a);
                stack.push(c);
                stack.push(b);
                stack.push(a);
                break;
            }
            case 92: {
                // TODO currently we don't know the stack types
                op = "dup2";
                Token a = stack.pop();
                Token b = stack.pop();
                stack.push(b);
                stack.push(a);
                stack.push(b);
                stack.push(a);
                break;
            }
            case 93: {
                // TODO currently we don't know the stack types
                op = "dup2_x1";
                Token a = stack.pop();
                Token b = stack.pop();
                Token c = stack.pop();
                stack.push(b);
                stack.push(a);
                stack.push(c);
                stack.push(b);
                stack.push(a);
                break;
            }
            case 94: {
                // TODO currently we don't know the stack types
                op = "dup2_x2";
                Token a = stack.pop();
                Token b = stack.pop();
                Token c = stack.pop();
                Token d = stack.pop();
                stack.push(b);
                stack.push(a);
                stack.push(d);
                stack.push(c);
                stack.push(b);
                stack.push(a);
                break;
            }
            case 95: {
                op = "swap";
                Token a = stack.pop();
                Token b = stack.pop();
                stack.push(a);
                stack.push(b);
                break;
            }
            case 96: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "iadd";
                stack.push(Operation.get(a, Operation.Type.ADD, b));
                break;
            }
            case 97: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "ladd";
                stack.push(Operation.get(a, Operation.Type.ADD, b));
                break;
            }
            case 98: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "fadd";
                stack.push(Operation.get(a, Operation.Type.ADD, b));
                break;
            }
            case 99: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "dadd";
                stack.push(Operation.get(a, Operation.Type.ADD, b));
                break;
            }
            case 100: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "isub";
                stack.push(Operation.get(a, Operation.Type.SUBTRACT, b));
                break;
            }
            case 101: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "lsub";
                stack.push(Operation.get(a, Operation.Type.SUBTRACT, b));
                break;
            }
            case 102: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "fsub";
                stack.push(Operation.get(a, Operation.Type.SUBTRACT, b));
                break;
            }
            case 103: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "dsub";
                stack.push(Operation.get(a, Operation.Type.SUBTRACT, b));
                break;
            }
            case 104: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "imul";
                stack.push(Operation.get(a, Operation.Type.MULTIPLY, b));
                break;
            }
            case 105: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "lmul";
                stack.push(Operation.get(a, Operation.Type.MULTIPLY, b));
                break;
            }
            case 106: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "fmul";
                stack.push(Operation.get(a, Operation.Type.MULTIPLY, b));
                break;
            }
            case 107: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "dmul";
                stack.push(Operation.get(a, Operation.Type.MULTIPLY, b));
                break;
            }
            case 108: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "idiv";
                stack.push(Operation.get(a, Operation.Type.DIVIDE, b));
                break;
            }
            case 109: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "ldiv";
                stack.push(Operation.get(a, Operation.Type.DIVIDE, b));
                break;
            }
            case 110: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "fdiv";
                stack.push(Operation.get(a, Operation.Type.DIVIDE, b));
                break;
            }
            case 111: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "ddiv";
                stack.push(Operation.get(a, Operation.Type.DIVIDE, b));
                break;
            }
            case 112: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "irem";
                stack.push(Operation.get(a, Operation.Type.MOD, b));
                break;
            }
            case 113: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "lrem";
                stack.push(Operation.get(a, Operation.Type.MOD, b));
                break;
            }
            case 114: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "frem";
                stack.push(Operation.get(a, Operation.Type.MOD, b));
                break;
            }
            case 115: {
                Token b = stack.pop();
                Token a = stack.pop();
                op = "drem";
                stack.push(Operation.get(a, Operation.Type.MOD, b));
                break;
            }
            // case 116:
            // op = "ineg";
            // break;
            // case 117:
            // op = "lneg";
            // break;
            // case 118:
            // op = "fneg";
            // break;
            // case 119:
            // op = "dneg";
            // break;
            // case 120:
            // op = "ishl";
            // break;
            // case 121:
            // op = "lshl";
            // break;
            // case 122:
            // op = "ishr";
            // break;
            // case 123:
            // op = "lshr";
            // break;
            // case 124:
            // op = "iushr";
            // break;
            // case 125:
            // op = "lushr";
            // break;
            // case 126:
            // op = "iand";
            // break;
            // case 127:
            // op = "land";
            // break;
            // case 128:
            // op = "ior";
            // break;
            // case 129:
            // op = "lor";
            // break;
            // case 130:
            // op = "ixor";
            // break;
            // case 131:
            // op = "lxor";
            // break;
            // case 132: {
            // int var = readByte();
            // int off = (byte) readByte();
            // op = "iinc " + var + " " + off;
            // break;
            // }
            // case 133:
            // op = "i2l";
            // break;
            // case 134:
            // op = "i2f";
            // break;
            // case 135:
            // op = "i2d";
            // break;
            // case 136:
            // op = "l2i";
            // break;
            // case 137:
            // op = "l2f";
            // break;
            // case 138:
            // op = "l2d";
            // break;
            // case 139:
            // op = "f2i";
            // break;
            // case 140:
            // op = "f2l";
            // break;
            // case 141:
            // op = "f2d";
            // break;
            // case 142:
            // op = "d2i";
            // break;
            // case 143:
            // op = "d2l";
            // break;
            // case 144:
            // op = "d2f";
            // break;
            // case 145:
            // op = "i2b";
            // break;
            // case 146:
            // op = "i2c";
            // break;
            // case 147:
            // op = "i2s";
            // break;
            case 148: {
                Token b = stack.pop(), a = stack.pop();
                stack.push(new Function("SIGN", Operation.get(a, Operation.Type.SUBTRACT, b)));
                op = "lcmp";
                break;
            }
            // case 149:
            // op = "fcmpl";
            // break;
            // case 150:
            // op = "fcmpg";
            // break;
            // case 151:
            // op = "dcmpl";
            // break;
            // case 152:
            // op = "dcmpg";
            // break;
            case 153:
                condition = true;
                nextPc = getAbsolutePos(pos, readShort());
                stack.push(Operation.get(stack.pop(), Operation.Type.EQUALS, ConstantNumber.get(0)));
                op = "ifeq " + nextPc;
                break;
            case 154:
                condition = true;
                nextPc = getAbsolutePos(pos, readShort());
                stack.push(Operation.get(stack.pop(), Operation.Type.NOT_EQUALS, ConstantNumber.get(0)));
                op = "ifne " + nextPc;
                break;
            case 155:
                condition = true;
                nextPc = getAbsolutePos(pos, readShort());
                stack.push(Operation.get(stack.pop(), Operation.Type.SMALLER, ConstantNumber.get(0)));
                op = "iflt " + nextPc;
                break;
            case 156:
                condition = true;
                nextPc = getAbsolutePos(pos, readShort());
                stack.push(Operation.get(stack.pop(), Operation.Type.BIGGER_EQUALS, ConstantNumber.get(0)));
                op = "ifge " + nextPc;
                break;
            case 157:
                condition = true;
                nextPc = getAbsolutePos(pos, readShort());
                stack.push(Operation.get(stack.pop(), Operation.Type.BIGGER, ConstantNumber.get(0)));
                op = "ifgt " + nextPc;
                break;
            case 158:
                condition = true;
                nextPc = getAbsolutePos(pos, readShort());
                stack.push(Operation.get(stack.pop(), Operation.Type.SMALLER_EQUALS, ConstantNumber.get(0)));
                op = "ifle " + nextPc;
                break;
            case 159: {
                condition = true;
                nextPc = getAbsolutePos(pos, readShort());
                Token b = stack.pop(), a = stack.pop();
                stack.push(Operation.get(a, Operation.Type.EQUALS, b));
                op = "if_icmpeq " + nextPc;
                break;
            }
            case 160: {
                condition = true;
                nextPc = getAbsolutePos(pos, readShort());
                Token b = stack.pop(), a = stack.pop();
                stack.push(Operation.get(a, Operation.Type.NOT_EQUALS, b));
                op = "if_icmpne " + nextPc;
                break;
            }
            case 161: {
                condition = true;
                nextPc = getAbsolutePos(pos, readShort());
                Token b = stack.pop(), a = stack.pop();
                stack.push(Operation.get(a, Operation.Type.SMALLER, b));
                op = "if_icmplt " + nextPc;
                break;
            }
            case 162: {
                condition = true;
                nextPc = getAbsolutePos(pos, readShort());
                Token b = stack.pop(), a = stack.pop();
                stack.push(Operation.get(a, Operation.Type.BIGGER_EQUALS, b));
                op = "if_icmpge " + nextPc;
                break;
            }
            case 163: {
                condition = true;
                nextPc = getAbsolutePos(pos, readShort());
                Token b = stack.pop(), a = stack.pop();
                stack.push(Operation.get(a, Operation.Type.BIGGER, b));
                op = "if_icmpgt " + nextPc;
                break;
            }
            case 164: {
                condition = true;
                nextPc = getAbsolutePos(pos, readShort());
                Token b = stack.pop(), a = stack.pop();
                stack.push(Operation.get(a, Operation.Type.SMALLER_EQUALS, b));
                op = "if_icmple " + nextPc;
                break;
            }
            case 165: {
                condition = true;
                nextPc = getAbsolutePos(pos, readShort());
                Token b = stack.pop(), a = stack.pop();
                stack.push(Operation.get(a, Operation.Type.EQUALS, b));
                op = "if_acmpeq " + nextPc;
                break;
            }
            case 166: {
                condition = true;
                nextPc = getAbsolutePos(pos, readShort());
                Token b = stack.pop(), a = stack.pop();
                stack.push(Operation.get(a, Operation.Type.NOT_EQUALS, b));
                op = "if_acmpne " + nextPc;
                break;
            }
            case 167:
                nextPc = getAbsolutePos(pos, readShort());
                op = "goto " + nextPc;
                break;
            // case 168:
            // // TODO not supported yet
            // op = "jsr " + getAbsolutePos(pos, readShort());
            // break;
            // case 169:
            // // TODO not supported yet
            // op = "ret " + readByte();
            // break;
            // case 170: {
            // int start = pos;
            // pos += 4 - ((pos - startByteCode) & 3);
            // int def = readInt();
            // int low = readInt(), high = readInt();
            // int n = high - low + 1;
            // op = "tableswitch default:" + getAbsolutePos(start, def);
            // StringBuilder buff = new StringBuilder();
            // for (int i = 0; i < n; i++) {
            // buff.append(' ').append(low++).
            // append(":").
            // append(getAbsolutePos(start, readInt()));
            // }
            // op += buff.toString();
            // // pos += n * 4;
            // break;
            // }
            // case 171: {
            // int start = pos;
            // pos += 4 - ((pos - startByteCode) & 3);
            // int def = readInt();
            // int n = readInt();
            // op = "lookupswitch default:" + getAbsolutePos(start, def);
            // StringBuilder buff = new StringBuilder();
            // for (int i = 0; i < n; i++) {
            // buff.append(' ').
            // append(readInt()).
            // append(":").
            // append(getAbsolutePos(start, readInt()));
            // }
            // op += buff.toString();
            // // pos += n * 8;
            // break;
            // }
            case 172:
                op = "ireturn";
                endOfMethod = true;
                break;
            case 173:
                op = "lreturn";
                endOfMethod = true;
                break;
            case 174:
                op = "freturn";
                endOfMethod = true;
                break;
            case 175:
                op = "dreturn";
                endOfMethod = true;
                break;
            case 176:
                op = "areturn";
                endOfMethod = true;
                break;
            case 177:
                op = "return";
                // no value returned
                stack.push(null);
                endOfMethod = true;
                break;
            // case 178:
            // op = "getstatic " + getField(readShort());
            // break;
            // case 179:
            // op = "putstatic " + getField(readShort());
            // break;
            case 180: {
                String field = getField(readShort());
                Token p = stack.pop();
                String s = p + "." + field.substring(field.lastIndexOf('.') + 1, field.indexOf(' '));
                if (s.startsWith("this.")) {
                    s = s.substring(5);
                }
                stack.push(Variable.get(s, fieldMap.get(s)));
                op = "getfield " + field;
                break;
            }
            // case 181:
            // op = "putfield " + getField(readShort());
            // break;
            case 182: {
                String method = getMethod(readShort());
                op = "invokevirtual " + method;
                if (method.equals("java/lang/String.equals (Ljava/lang/Object;)Z")) {
                    Token a = stack.pop();
                    Token b = stack.pop();
                    stack.push(Operation.get(a, Operation.Type.EQUALS, b));
                } else if (method.equals("java/lang/Integer.intValue ()I")) {
                    // ignore
                } else if (method.equals("java/lang/Long.longValue ()J")) {
                    // ignore
                }
                break;
            }
            case 183: {
                String method = getMethod(readShort());
                op = "invokespecial " + method;
                break;
            }
            case 184:
                op = "invokestatic " + getMethod(readShort());
                break;
            // case 185: {
            // int methodRef = readShort();
            // readByte();
            // readByte();
            // op = "invokeinterface " + getMethod(methodRef);
            // break;
            // }
            case 187: {
                String className = constantPool[constantPool[readShort()].intValue()].toString();
                op = "new " + className;
                break;
            }
            // case 188:
            // op = "newarray " + readByte();
            // break;
            // case 189:
            // op = "anewarray " + cpString[readShort()];
            // break;
            // case 190:
            // op = "arraylength";
            // break;
            // case 191:
            // op = "athrow";
            // break;
            // case 192:
            // op = "checkcast " + cpString[readShort()];
            // break;
            // case 193:
            // op = "instanceof " + cpString[readShort()];
            // break;
            // case 194:
            // op = "monitorenter";
            // break;
            // case 195:
            // op = "monitorexit";
            // break;
            // case 196: {
            // opCode = readByte();
            // switch (opCode) {
            // case 21:
            // op = "wide iload " + readShort();
            // break;
            // case 22:
            // op = "wide lload " + readShort();
            // break;
            // case 23:
            // op = "wide fload " + readShort();
            // break;
            // case 24:
            // op = "wide dload " + readShort();
            // break;
            // case 25:
            // op = "wide aload " + readShort();
            // break;
            // case 54:
            // op = "wide istore " + readShort();
            // break;
            // case 55:
            // op = "wide lstore " + readShort();
            // break;
            // case 56:
            // op = "wide fstore " + readShort();
            // break;
            // case 57:
            // op = "wide dstore " + readShort();
            // break;
            // case 58:
            // op = "wide astore " + readShort();
            // break;
            // case 132: {
            // int var = readShort();
            // int off = (short) readShort();
            // op = "wide iinc " + var + " " + off;
            // break;
            // }
            // case 169:
            // op = "wide ret " + readShort();
            // break;
            // default:
            // throw new IciqlException(
            // "Unsupported wide opCode " + opCode);
            // }
            // break;
            // }
            // case 197:
            // op = "multianewarray " + cpString[readShort()] + " " + readByte();
            // break;
            // case 198: {
            // condition = true;
            // nextPc = getAbsolutePos(pos, readShort());
            // Token a = stack.pop();
            // stack.push("(" + a + " IS NULL)");
            // op = "ifnull " + nextPc;
            // break;
            // }
            // case 199: {
            // condition = true;
            // nextPc = getAbsolutePos(pos, readShort());
            // Token a = stack.pop();
            // stack.push("(" + a + " IS NOT NULL)");
            // op = "ifnonnull " + nextPc;
            // break;
            // }
            case 200:
                op = "goto_w " + getAbsolutePos(pos, readInt());
                break;
            case 201:
                op = "jsr_w " + getAbsolutePos(pos, readInt());
                break;
            default:
                throw new IciqlException("Unsupported opCode " + opCode);
        }
        debug("    " + startPos + ": " + op);
    }

    private void setVariable(int x, Token value) {
        while (x >= variables.size()) {
            variables.add(Variable.get("p" + variables.size(), null));
        }
        variables.set(x, value);
    }

    private Token getVariable(int x) {
        if (x == 0) {
            return Variable.THIS;
        }
        while (x >= variables.size()) {
            variables.add(Variable.get("p" + variables.size(), null));
        }
        return variables.get(x);
    }

    private String getField(int fieldRef) {
        int field = constantPool[fieldRef].intValue();
        int classIndex = field >>> 16;
        int nameAndType = constantPool[field & 0xffff].intValue();
        String className = constantPool[constantPool[classIndex].intValue()] + "."
                + constantPool[nameAndType >>> 16] + " " + constantPool[nameAndType & 0xffff];
        return className;
    }

    private String getMethod(int methodRef) {
        int method = constantPool[methodRef].intValue();
        int classIndex = method >>> 16;
        int nameAndType = constantPool[method & 0xffff].intValue();
        String className = constantPool[constantPool[classIndex].intValue()] + "."
                + constantPool[nameAndType >>> 16] + " " + constantPool[nameAndType & 0xffff];
        return className;
    }

    private Constant getConstant(int constantRef) {
        Constant c = constantPool[constantRef];
        switch (c.getType()) {
            case INT:
            case FLOAT:
            case DOUBLE:
            case LONG:
                return c;
            case STRING_REF:
                return constantPool[c.intValue()];
            default:
                throw new IciqlException("Not a constant: " + constantRef);
        }
    }

    private String readString() {
        int size = readShort();
        byte[] buff = data;
        int p = pos, end = p + size;
        char[] chars = new char[size];
        int j = 0;
        for (; p < end; j++) {
            int x = buff[p++] & 0xff;
            if (x < 0x80) {
                chars[j] = (char) x;
            } else if (x >= 0xe0) {
                chars[j] = (char) (((x & 0xf) << 12) + ((buff[p++] & 0x3f) << 6) + (buff[p++] & 0x3f));
            } else {
                chars[j] = (char) (((x & 0x1f) << 6) + (buff[p++] & 0x3f));
            }
        }
        pos = p;
        return new String(chars, 0, j);
    }

    private int getAbsolutePos(int start, int offset) {
        return start - startByteCode - 1 + (short) offset;
    }

    private int readByte() {
        return data[pos++] & 0xff;
    }

    private int readShort() {
        byte[] buff = data;
        return ((buff[pos++] & 0xff) << 8) + (buff[pos++] & 0xff);
    }

    private int readInt() {
        byte[] buff = data;
        return (buff[pos++] << 24) + ((buff[pos++] & 0xff) << 16) + ((buff[pos++] & 0xff) << 8)
                + (buff[pos++] & 0xff);
    }

    private long readLong() {
        return ((long) (readInt()) << 32) + (readInt() & 0xffffffffL);
    }

}
